/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#ifndef SEARCH_CC
#define SEARCH_CC


#include "io.h"
#include "error.h"
#include "search.h"
#include "settings.h"
#include "crypt.h"
#include "simple.h"
#include "gui_al.h"
#include "gui_io.h"
#include "months.h"
#include "plugins.h"
#include "cryptography.h"
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include "journal.h"


#define __MAX_ALLOWED_RESULTS 250

struct elog_srch_result **_buf = NULL;
int bufSize = 0;

float _allowed_diff = .2;
int _case_Sensitive = 0;	//1 for yes, 0 for no; 1 is less work.

char *_dir = NULL;

void (*elog_srch_functor) (void *) = NULL;
int _srch_lyrical = 0;		//if 1, only search in lyrically true entries.



struct _native_entry *traverse(const char *directory,
			       struct _native_entry *head)
{
	DIR *dir = opendir(directory);
	struct dirent *ent;
	struct _native_entry *last = head;
	while ((ent = readdir(dir)) != NULL) {
		if (ent->d_name[0] != '.') {
			int c = 0;
			while (directory[c] != '\0')
				++c;
			int j = 0;
			while (ent->d_name[j] != '\0')
				++j;
			char *s =
			    (char *) malloc(sizeof(char) * (c + j + 5));
			s[0] = '\0';
			strcat(s, directory);
			strcat(s, "/");
			strcat(s, ent->d_name);
			if (chdir(s) == -1) {
				struct _native_entry *q =
				    malloc(sizeof *q);
				q->location = s;
				q->next = NULL;
				last->next = q;
				last = q;
			} else {
				last = traverse(s, last);
			}
		}
	}
	closedir(dir);
	return last;
}


struct elog_srch_result *searcher(const char *term, const char *fileName,
				  unsigned int len)
{

	struct dict *doc = elog_crypt_open_dry(fileName);
	if (doc == NULL)
		return NULL;

	if (dict_check(doc, "date", "entry text", "subject", NULL)
	||	dict_rcheck(doc, "date", "year", NULL)
	||	dict_rcheck(doc, "date", "month", NULL)
	||	dict_rcheck(doc, "date", "day", NULL)) {
		char *msg;
		elog_sp_cat(&msg, "Bad document:", fileName, NULL);
		elog_wrn_print(msg);
		free(msg);
		return NULL;				
	}
	unsigned char *text = (unsigned char *) (dict_get(doc, "entry text"));

	struct elog_srch_result *res = elog_srch_result_new();
	elog_sp_cat(&(res->fileName), dict_get(doc, "fileName"), NULL);
	res->txt = elog_sp_toChar(text, len);


	if (*(int *)(dict_rget(doc, "date", "year", NULL)) == 0 && 
		*(int *)(dict_rget(doc, "date", "day", NULL)) == 0)	//no date
	{			//compatibility code for old files.
		char *realDay;	//taking off the /day at the end, this gives
		//compatibility with full filenames.
		char *t;
		elog_sp_cat(&t, fileName, NULL);
		*(strrchr(t, '/')) = '\0';

		elog_sp_cat(&realDay, t, NULL);
		free(t);


		struct cal_date *date1 = elog_gui_io_getDate(realDay);
		free(realDay);
		struct dict *date = dict_get(doc, "date");
		dict_set(date, "month", HEAP_INT(date1->month));
		dict_set(date, "year", HEAP_INT(date1->year));
		dict_set(date, "day", HEAP_INT(date1->day));
		free(date1);
	}

	res->year = *(int *)(dict_rget(doc, "date", "year", NULL));
	res->month = *(int *)(dict_rget(doc, "date", "month", NULL));
	res->day = *(int *)(dict_rget(doc, "date", "day", NULL));

	if (dict_get(doc, "subject") != NULL)
		elog_sp_cat(&(res->name), dict_get(doc, "subject"), NULL);
	else
		elog_sp_cat(&(res->name), "", NULL);


	dict_set_free(doc, "entry text", 0); //so we can hold the text!
	int *results =
	    elog_srch_listResults(elog_sp_toChar(text, len), term);
	int occur;
	for (occur = 0; results[occur] != -1; occur += 2);	//intentional
	//counting up occurances, we don't care what they are!

	free(results);

	dict_unref(doc);

	res->occurances = occur / 2;
	
	return res;
}

bool stringMatch(char *c1, int len, int offset, const char *c2)
{
	int c;
	for (c = 0; c < len; ++c)
		if (c1[offset + c] != c2[c])
			return 0;
	return 1;
}
void search(struct elog_io_dir *dir, const char *term)
{
	elog_gui_al_lock();
	elog_gui_al_progress_pulse();
	elog_gui_al_unlock();

	int j;
	for (j = 0; dir->dir[j] != NULL; ++j) {
		char *file;
		elog_sp_cat(&file, dir->path, dir->dir[j], NULL);
		int len = 0;
		if (dir->dir[j][0] != '/') {
			struct elog_srch_result *res =
			    searcher(term, file, len);
			if (res != NULL) {

				if (res->occurances > 0) {
					elog_srch_functor(res);
					/*elog_gui_al_lock();
					   elog_gui_al_addResult(res);
					   elog_gui_al_unlock();
					   elog_gui_io_view_add_result(res->year, res->month, res->day); */
				}

				elog_srch_result_free(res);
			}
		}
		free(file);
	}
	int c;
	for (c = 0; dir->subDir[c] != NULL; ++c) {
		search(dir->subDir[c], term);
	}

}


void elog_srch_search(const char *directory, const char *term,
		      void (*functor) (void *))
{
	elog_srch_functor = functor;
	struct elog_io_dir *dir = elog_io_recurseDir(directory, directory);
	search(dir, term);
	elog_io_dir_free(dir);
}

const char *elog_srch_setDir(const char *directory)
{
	if (!directory)
		return _dir;
	if (_dir != NULL)
		free(_dir);
	elog_sp_cat(&_dir, directory, NULL);
	return NULL;
}




void elog_srch_buffer(const char *directory)
{
/*	struct elog_io_dir *dirs = elog_io_recurseDir(directory, directory);
	int j;
	for (j=0; dirs->dir[j] != NULL; ++j)
	{
		char *file;
		elog_sp_cat(&file, dir->path, dir->dir[j], NULL);
		int len;
		if (dir->dir[j][0] != '/')
		{
			struct elog_xml_doc *doc = elog_xml_open(fileName, 0);
			char *text = elog_io_readRestofFile(doc->handle, &len);
			decryptText(text, len);
			struct elog_srch_result *res = elog_srch_result_new();			
			elog_sp_cat(&(res->fileName), doc->fileName, NULL);
			res->txt = text;
			char **arr = elog_sp_breakToArr(doc->fileName, '/');
			int last = 0;
			while (arr[last] != NULL) ++last; //finding last element
			--last;
			res->month = arr[last - 1];
			res->year = arr[last - 2];
			elog_sp_cat(&(res->name), doc->subject);
			
			elog_xml_doc_free(doc);
		}
	}		
	int c;
	for (c=0; dir->subDir[c] != NULL; ++c)
	{
		search(dir->subDir[c], term);
	}
	
	elog_io_dir_free(dirs);*/
}
void elog_srch_clearBuffer()
{
	int c;
	for (c = 0; _buf[c] != NULL; ++c)
		elog_srch_result_free(_buf[c]);
	free(_buf);
	_buf = NULL;
}
struct elog_srch_result *elog_srch_result_new()
{
	struct elog_srch_result *res = malloc((sizeof *res));
	res->name = NULL;
	res->name2 = NULL;
	res->fileName = NULL;
	res->txt = NULL;
	return res;
}
void elog_srch_result_free(struct elog_srch_result *res)
{
	if (res->name != NULL)
		free(res->name);
	if (res->name2 != NULL)
		free(res->name2);
	if (res->fileName != NULL)
		free(res->fileName);
	if (res->txt != NULL)
		free(res->txt);
	free(res);
}

int elog_srch_lyrical(const char *text)
{

	int l;

	int loc = 0;
	for (l = 0; l < _ELOG_SRCH_LINES && text[loc] != '\0';) {
		int c = 0;
		while (1) {
			if (text[loc] == '\n') {
				++loc;
				break;
			}
			if (text[loc] == '\0')
				break;
			++loc;
			++c;
		}

		if (c < _ELOG_SRCH_MAXLINE)
			++l;
		else if (c > 0)
			--l;
	}
	if (l == _ELOG_SRCH_LINES)
		return 1;
	else
		return 0;
}

void __run_through_haystack(char *haystack, const char *needle,
			    unsigned int *loc, int *list, int len_needle,
			    int allowed_diff)
{
	if (*loc >= __MAX_ALLOWED_RESULTS)
		return;		//too much, bail.



	char *s =
	    elog_srch_strstr(haystack, needle, allowed_diff, len_needle);
	do {
		if (s == NULL)
			break;
		int j;
		for (j = 0; j < len_needle; ++j) {
			if (s[j] != '\0')
				s[j] = '~';	//clearing out result so it can't be counted twice!
			else
				return;	//leaving cause we hit the end of text.
		}

		list[*loc] = (s - haystack) / (sizeof *s);
		list[*loc + 1] = (s - haystack) / (sizeof *s) + len_needle;



		*loc += 2;
		if (*loc >= __MAX_ALLOWED_RESULTS)
			break;	//maximum search results
	} while ((s =
		  elog_srch_strstr(&s[1], needle, allowed_diff,
				   len_needle)));

}

int *elog_srch_listResults(const char *haystack, const char *needle)
{
	int len_needle = strlen(needle);
	int allowed_diff = (_allowed_diff / 4.0) * (float) len_needle;

	int *list = malloc((sizeof *list) * 256);

	if (_srch_lyrical) {
		if (!elog_srch_lyrical(haystack)) {
			list[0] = -1;
			return list;
		}
	}

	char *hay;
	char *term;
	if (_case_Sensitive == 0)	//if we aren't case sensitive
	{
		hay = malloc((sizeof *hay) * (strlen(haystack) + 1));
		term = malloc((sizeof *term) * (strlen(needle) + 1));

		int c;
		for (c = 0; haystack[c] != '\0'; ++c)
			hay[c] = toupper(haystack[c]);
		hay[c] = '\0';
		for (c = 0; needle[c] != '\0'; ++c)
			term[c] = toupper(needle[c]);
		term[c] = '\0';

	} else {
		elog_sp_cat(&hay, haystack, NULL);
		elog_sp_cat(&term, needle, NULL);	//so that we can free it without looking!
	}


	char **terms_tmp = elog_sp_breakToArr(term, ' ');


	char **terms = malloc((sizeof *terms) * 256);
	int i;
	int l = 0;
	for (i = 0; terms_tmp[i]; ++i) {
		terms[l] = elog_sp_strReplace(terms_tmp[i], "+", " ");

		++l;
		if (l > 255)
			break;
		free(terms_tmp[i]);
	}
	free(terms_tmp);
	terms[l] = NULL;

	//terms now contains a list of all words given.

	char *term_minus_symbols = elog_sp_strReplace(term, "+", " ");

	unsigned int c = 0;
	__run_through_haystack(hay, term_minus_symbols, &c, list,
			       strlen(term), allowed_diff);


	int j;
	for (j = 0; c < __MAX_ALLOWED_RESULTS && terms[j] != NULL; ++j)	//searching each term without preference.
	{
		__run_through_haystack(hay, terms[j], &c, list,
				       strlen(terms[j]), allowed_diff);
	}
	list[c] = -1;

	free(hay);
	elog_sp_ArrFree(terms);
	free(term);

	return list;
}


char *elog_srch_strstr(const char *haystack, const char *needle,
		       int allowed_diff, int len_needle)
{
	char last = '\0';
	int c;
	for (c = 0; haystack[c] != '\0'; ++c) {
		int j;
		if (last == ' ' || last == '\0' || last == '\n'
		    || last == '\t') {
			int diff = 0;
			for (j = 0;
			     j < len_needle && haystack[c + j] != '\0';
			     ++j) {
				if (haystack[c + j] != ' '
				    && haystack[c + j] != '\t'
				    && haystack[c + j] != '\n') {
					if (haystack[c + j] != needle[j])
						diff++;
				} else {
					if (haystack[c + j] == ' ' && needle[j] == ' ');	//intetional
					else {
						diff = allowed_diff + 1;	//this is not a result
						break;	//exit this loop.
					}
				}
			}
			if (diff <= allowed_diff)
				return (char *) &(haystack[c]);
		}
		last = haystack[c];
	}
	return NULL;
}

int elog_srch_caseSensitive(int set)
{
	if (set < 0)
		return _case_Sensitive;
	_case_Sensitive = set;
	elog_set_set_int("SearchCase", set);
	return -1;
}
float elog_srch_accuracy(float set)
{
	if (set < 0.0)
		return (1.0 - _allowed_diff);
	_allowed_diff = 1.0 - set;
	elog_set_set_int("SearchAccur", (int) (_allowed_diff * 10.0));
	return -1.0;
}
int elog_srch_lyric(int set)
{
	if (set < 0)
		return _srch_lyrical;
	_srch_lyrical = set;
	return -1;
}


/*******The new wave of search:***********/

struct elog_srch_instr *elog_srch_instr_new()
{
	struct elog_srch_instr *i = malloc(sizeof *i);
	i->list = NULL;
	return i;
}

void elog_srch_instr_free(struct elog_srch_instr *instr)
{
	free(instr);
}


struct _srch_incr_lst *_srch_get_list(struct _srch_incr_lst *root,
				      struct elog_io_dir *dir)
{
	if (!root)
		root = malloc(sizeof *root);
	struct _srch_incr_lst *p = root;
	p->next = NULL;
	int j;
	for (j = 0; dir->dir[j] != NULL; ++j) {
		if (dir->dir[j][0] != '/') {
			char *file;
			elog_sp_cat(&file, dir->path, dir->dir[j], NULL);
			p->next = malloc(sizeof *p);
			p->fileName = file;
			p = p->next;
			p->next = NULL;
		}
	}
	int c;
	for (c = 0; dir->subDir[c] != NULL; ++c) {
		struct _srch_incr_lst *t =
		    _srch_get_list(NULL, dir->subDir[c]);
		if (t) {
			p->fileName = t->fileName;
			p->next = t->next;
			free(t);
			while (p->next)
				p = p->next;
		}
	}

	p->fileName = NULL;
	if (j || c)
		return root;

	free(root);
	return NULL;
}

int elog_srch_incremental_search(struct elog_srch_instr *instr)
{
	if (instr->list) {
		elog_gui_al_lock();
		elog_gui_al_progress_pulse();
		elog_gui_al_unlock();
		int c = 0;
		while (c < 5) {
			unsigned int len = 0;
			struct elog_srch_result *res =
			    searcher(instr->term, instr->list->fileName,
				     len);
			if (res != NULL) {
				if (res->occurances > 0)
					instr->func(res);

				elog_srch_result_free(res);
			}
			++c;
			struct _srch_incr_lst *p = instr->list->next;
			free(instr->list->fileName);
			free(instr->list);
			instr->list = p;
			if (!(p->fileName))
				break;
		}
		if (instr->list->fileName)
			return 1;
		else {
			elog_gui_al_lock();
			elog_gui_al_pop_status(instr->id);
			elog_gui_al_progress_set(0.0);
			elog_gui_al_unlock();
			free(instr->list);
			elog_srch_instr_free(instr);

			return 0;
		}
	} else {		//first run.
		struct elog_io_dir *dir =
		    elog_io_recurseDir(instr->directory, instr->directory);
		instr->list = _srch_get_list(NULL, dir);
		elog_io_dir_free(dir);
		char *str;
		elog_sp_cat(&str, "Searching for ", instr->term, "...",
			    NULL);
		elog_gui_al_lock();
		instr->id = elog_gui_al_push_status(str);
		elog_gui_al_unlock();
		free(str);
		return elog_srch_incremental_search(instr);
	}
	return 0;
}


#endif
